package cmp.compile;

import java.util.HashMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.io.*;
import java.sql.ResultSet;

import cmp.main.Globals;
import assem.DBConn;
import cmp.compile.CompileMain;
import cmp.compile.MethodUserDef;
import cmp.compile.panels.CompilePanel;
import util.Debug.ErrorReport;

public class MethodTransitive {
	 String groupFile = Globals.Compile.GROUP_FILE_TRANSITIVE;
	
	 public boolean run(int idx, DBConn db, CompilePanel panel) {
		String comment = panel.getMethodComment(idx);
		String [] settings = panel.getMethodSettings(idx).split(":");

		prefix = panel.getMethodPrefix(idx);						// Groups should be prefixed with this
		overlapCutoff = Integer.parseInt(settings[1]);
		similarity = Integer.parseInt(settings[2]);
		cmpDBC = db;
		cmpPanel = panel;
		blastFile = cmpPanel.getBlastPanel().getBlastFile();
		if (blastFile == null) return false;
		
		groupFile = cmpPanel.getCurrentProjectDirectory() + "/" + groupFile + 
				"." + prefix + "_" + overlapCutoff + "-" + similarity;
		
		CompileMain.PrtDateMsg("\nStart execution of Transitive");
		CompileMain.PrtSpMsg(1, "Prefix:     " + prefix);
		if (!comment.equals("")) CompileMain.PrtSpMsg(1, "Remark:    " + comment);
		CompileMain.PrtSpMsg(1, "Overlap:    " + overlapCutoff);
		CompileMain.PrtSpMsg(1, "Similarity: " + similarity);
		CompileMain.PrtSpMsg(1, "Blast File: " + blastFile);
		CompileMain.PrtSpMsg(1, "");
		long startTime = CompileMain.getTime();
			
		doSteps();
		
		// the following will load from file <project>/groupFile
		if (! new MethodUserDef().run(idx, groupFile, cmpDBC, cmpPanel)) return false;
		
		CompileMain.PrtDateMsgTime("Finish execution of Transitive", startTime);
		return true; 
	}
	
	 private boolean doSteps() {
		long t = CompileMain.getTime();
		CompileMain.PrtSpMsg(1, "Do transitive steps");
		
		try {
			readLengths();
			readBlast();
			writeGroups();			
			
			CompileMain.PrtSpMsgTime(1, "Finish transitive computation", t);
			return true;
		} 
		catch (Exception e) {
			ErrorReport.reportError(e, "Error on TC doSteps"); // writes msg to stdout and e to Cpave.error.log
			return false;
		}
	}
	/**********************************************************
	 * Read sequence lengths from database and enter into seqLenMap
	 */
	private void readLengths() {
	try {
		CompileMain.PrtSpMsg(2, "Get lengths from database");
				
		seqLenMap = new HashMap <String, Integer> ();
		ResultSet rs = cmpDBC.executeQuery("SELECT UTstr, aaLen " +
				" FROM unitrans WHERE aaSeq is NOT NULL");
		while (rs.next()) {
			seqLenMap.put(rs.getString(1), rs.getInt(2));
		}
		rs.close();		
		CompileMain.PrtSpMsg(3, " Read " + seqLenMap.size() + " sequence lengths");
	}
	catch (Exception e) {ErrorReport.die(e, "readLengths");}
	}
	/**********************************************************
	 * Read blast file and create:
	 * 	seqGrpMap for fast lookup
	 *  grpSeqMap is a list of seqIDs in a group
	 */
	private void readBlast() {
	try {
		CompileMain.PrtSpMsg(2, "Read blast file");

		BufferedReader reader = new BufferedReader(new FileReader(new File (blastFile)));
		int cntLoad=0, cntRead = 0, cntMinSim=0, cntMinOlap=0;
		int grpMax=1;
		String line;
		
		HashMap <String, Integer> seqGrpMap = new HashMap <String, Integer> (); // SeqName, grpID
		grpSeqMap = new HashMap <Integer, String> (); // grpID, list of SeqNames

		while ((line = reader.readLine()) != null) 
		{
			if (line.length() == 0 || line.charAt(0) == '#') continue; 

			cntRead++;
			if (cntRead % 10000 == 0) 
				System.err.print("   Read: " + cntRead + " Pairs: " + cntLoad + 
						" Clusters: " + grpSeqMap.size() + "\r");
			String tokens[] = line.split("\t");
			String blSeqID1 = tokens[0].trim();
			String blSeqID2 = tokens[1].trim();
			if (blSeqID1.equals(blSeqID2)) continue; // Ignore self-hit
			
			String seqID1 = getID(blSeqID1);
			String seqID2 = getID(blSeqID2);
			Double simPercent = Double.parseDouble(tokens[2]);
			int alignLen = Integer.parseInt(tokens[3]);
			
			if (missingFromDatabase(seqID1) || missingFromDatabase(seqID2)) 
				continue;
			cntFound++;		
		
			if (!seqGrpMap.containsKey(blSeqID1)) seqGrpMap.put(blSeqID1, 0);
			if (!seqGrpMap.containsKey(blSeqID2)) seqGrpMap.put(blSeqID2, 0);
			
			if (simPercent < similarity) {
				cntMinSim++;
				continue;
			}
			
			double olap1  = ((double)alignLen/(double)seqLenMap.get(seqID1)) * 100.0;
			double olap2  = ((double)alignLen/(double)seqLenMap.get(seqID2)) * 100.0;

			if (olap1 < overlapCutoff || olap2 < overlapCutoff) {
				cntMinOlap++;		
				continue;
			}
			cntLoad++;
			
			// Groups use pfxSeq so that the prefix is automatically part of output
			int grp1 = seqGrpMap.get(blSeqID1);
			int grp2 = seqGrpMap.get(blSeqID2);
	
			if (grp1 != 0 && grp1 == grp2) 	      // both in same group
				continue;
			
			if (grp1 != 0 && grp2 != 0) {			// merge
				int grpMer = (grp1 < grp2) ? grp1 : grp2;
				int grpDel = (grp1 > grp2) ? grp1 : grp2;
				String memMerList = grpSeqMap.get(grpMer);
				String memDelList = grpSeqMap.get(grpDel);
				grpSeqMap.put(grpDel, "dead");
				
				String [] members = memDelList.split(" ");
				for (int i=0; i< members.length; i++) {
					String m = members[i].trim();
					if (!m.equals("")) seqGrpMap.put(m, grpMer);
				}
				memMerList += " " + memDelList;
				grpSeqMap.put(grpMer, memMerList);
			}
			else if (grp1 != 0) {
				seqGrpMap.put(blSeqID2, grp1);
				grpSeqMap.put(grp1, grpSeqMap.get(grp1) + " " + blSeqID2);
			}
			else if (grp2 != 0) {
				seqGrpMap.put(blSeqID1, grp2);
				grpSeqMap.put(grp2, grpSeqMap.get(grp2) + " " + blSeqID1);
			}
			else {								// new
				grpSeqMap.put(grpMax, blSeqID1 + " " + blSeqID2);
				seqGrpMap.put(blSeqID1, grpMax);
				seqGrpMap.put(blSeqID2, grpMax);		
				grpMax++;
			}
		}
		System.out.println("                                                                           \r");
		CompileMain.PrtSpMsg(3, "Pairs: " + cntFound + " Used: " + cntLoad);
		CompileMain.PrtSpMsg(3, "Failed similarity: " + cntMinSim + " Failed overlap: " + cntMinOlap);
		CompileMain.PrtSpMsg(3, "Total groups: " + (grpSeqMap.size()-1));		
		
		seqGrpMap.clear();
		seqLenMap.clear();
	}
	catch (Exception e) {
		writeGroups();
		ErrorReport.die(e, "readBlast and create groups");
	}
	}

	private String getID(String id) {
		Pattern u = Pattern.compile("(\\S+)\\|(\\S+)"); 
		Matcher m = u.matcher(id);
		if (m.find()) return m.group(2);
		else return id;
	}
	private boolean missingFromDatabase(String seqID) {
		if (!seqLenMap.containsKey(seqID)) {
			if (cntMissing < 5) 
				CompileMain.PrtWarn("ID " + seqID + " in blast file but not in database");
			else if (cntMissing == 5) 
				CompileMain.PrtWarn("   suppress further missing messages");
			cntMissing++;
			if (cntFound == 0 && cntMissing > 20) 
				CompileMain.die("Appears to be the wrong blast file");
			return true;
		}
		return false;
	}
	
	/***********************************************************
	 * Write grpSeqs into a file with one group per line.
	 * The blast file has the taxo with each name, e.g. Pa|PaRi_0001
	 * So it does not need writing here. 
	 */
	 private void writeGroups() {
		try {
			CompileMain.PrtSpMsg(2, "Write groups to file " + groupFile);			
			PrintWriter outFH = new PrintWriter(new FileOutputStream(groupFile)); 
			int group=1;
			for (int idx : grpSeqMap.keySet()) {
				String mems = grpSeqMap.get(idx);
				if (!mems.equals("dead"))
					outFH.println(group + " " + grpSeqMap.get(idx));
				group++;
			}
			outFH.close();
			CompileMain.PrtSpMsg(3, "Wrote " + group + " groups");
			grpSeqMap.clear();
		}
		catch (Exception e) {
			ErrorReport.die(e, "write clusters to file");
		}
	}
	
	 private DBConn cmpDBC;			// database connection
	 private CompilePanel cmpPanel;	// get all parameters from this
	
	 private String prefix;
	 private int overlapCutoff, similarity;
	 private String blastFile;
	
	 private int cntFound=0, cntMissing=0;
	 private HashMap <String, Integer> seqLenMap;
	 private HashMap <Integer,String>  grpSeqMap;
}
